package bigdata.backend.datasource.service.impl;

import bigdata.backend.datasource.entity.Datasource;
import bigdata.backend.datasource.service.DatasourceService;
import bigdata.backend.datasource.service.SchemaSyncService;
import bigdata.backend.datasource.utils.*;
import bigdata.backend.utils.DbType;
import bigdata.backend.utils.TableNameValidator;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.regex.Pattern;

/**
 * 表结构同步服务实现类
 * <p>
 * 职责：
 * - 校验同步参数（表名、数据源等）
 * - 区分同构（相同数据库类型）与异构（不同数据库类型）同步
 * - 生成并执行目标建表语句
 * - 处理 PostgreSQL 的额外注释语句
 * - 新增能力：仅生成 DDL（供前端预览/编辑）
 * <p>
 * 注意：
 * - 本服务层不返回 Map，不处理 Web 响应格式
 * - 业务异常抛出 SchemaSyncException，由 Controller 统一捕获
 * - 系统异常（如数据库连接失败）也包装为 SchemaSyncException 向上抛出
 */
@Slf4j
@Service
public class SchemaSyncServiceImpl implements SchemaSyncService {

    @Autowired
    private DatasourceService datasourceService;

    /**
     * 同步表结构：从源数据源的源表同步到目标数据源的目标表
     * <p>
     * 此方法会：
     * 1. 校验参数
     * 2. 生成目标 DDL
     * 3. 在目标数据源中执行建表
     *
     * @param sourceDs     源数据源（必须已注册）
     * @param targetDs     目标数据源（必须已注册）
     * @param sourceTable  源表名（必填）
     * @param targetTable  目标表名（可为空，默认同源表名）
     * @return 同步结果（成功/失败 + SQL + 消息）
     * @throws SchemaSyncException 业务校验失败或同步过程中发生错误
     */
    @Override
    public SyncTableResult syncTable(Datasource sourceDs, Datasource targetDs, String sourceTable, String targetTable) {
        // ====== 1. 参数校验与预处理 ======
        validateAndPrepare(sourceDs, targetDs, sourceTable, targetTable);

        // 提取标准化后的表名
        sourceTable = TableNameValidator.validate(sourceTable.trim());
        targetTable = (targetTable == null || targetTable.trim().isEmpty())
                ? sourceTable
                : TableNameValidator.validate(targetTable.trim());

        // 获取数据库类型
        DbType sourceType = DbType.fromString(sourceDs.getDbType());
        DbType targetType = DbType.fromString(targetDs.getDbType());

        log.info("开始同步表结构: {}({}) -> {}({})",
                sourceDs.getSourceName(), sourceTable, targetDs.getSourceName(), targetTable);

        try {
            if (sourceType == targetType) {
                // 同构同步：复用源库建表语句（替换表名/schema）
                return handleHomogeneousSync(sourceDs, targetDs, sourceTable, targetTable, sourceType);
            } else {
                // 异构同步：解析字段元数据，重建目标建表语句
                return handleHeterogeneousSync(sourceDs, targetDs, sourceTable, targetTable, sourceType, targetType);
            }
        } catch (Exception e) {
            log.error("表结构同步过程中发生未预期异常", e);
            throw new SchemaSyncException("同步失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成目标数据源的建表 DDL（不执行）
     * <p>
     * 此方法适用于：
     * - 前端预览建表语句
     * - 用户手动编辑后再提交执行
     * - 自动化脚本生成
     *
     * @param sourceDs     源数据源
     * @param targetDs     目标数据源
     * @param sourceTable  源表名
     * @param targetTable  目标表名（可为空，默认同源表名）
     * @return 生成的完整 DDL 语句（字符串）
     * @throws SchemaSyncException 参数校验失败或生成过程中出错
     */
    public String generateCreateTableDdl(
            Datasource sourceDs,
            Datasource targetDs,
            String sourceTable,
            String targetTable) {

        // 1. 参数校验（复用相同逻辑，但不检查目标表是否存在，因为只是生成）
        if (sourceTable == null || sourceTable.trim().isEmpty()) {
            throw new SchemaSyncException("源表名不能为空");
        }

        // 2. 表名标准化
        sourceTable = TableNameValidator.validate(sourceTable.trim());
        targetTable = (targetTable == null || targetTable.trim().isEmpty())
                ? sourceTable
                : TableNameValidator.validate(targetTable.trim());

        // 3. 获取数据库类型
        DbType sourceType = DbType.fromString(sourceDs.getDbType());
        DbType targetType = DbType.fromString(targetDs.getDbType());

        log.debug("生成建表 DDL: {}({}) -> {}({})", sourceDs.getSourceName(), sourceTable, targetDs.getSourceName(), targetTable);

        // 4. 根据是否同构，调用对应的 DDL 生成方法
        if (sourceType == targetType) {
            return generateHomogeneousDdl(sourceDs, targetDs, sourceTable, targetTable, sourceType);
        } else {
            return generateHeterogeneousDdl(sourceDs, targetDs, sourceTable, targetTable, sourceType, targetType);
        }
    }

    // ==============================
    // Private: 参数校验（仅用于 syncTable，因为要检查目标表是否存在）
    // ==============================

    /**
     * 参数校验与前置检查（仅用于实际同步场景）
     */
    private void validateAndPrepare(Datasource sourceDs, Datasource targetDs, String sourceTable, String targetTable) {
        if (sourceTable == null || sourceTable.trim().isEmpty()) {
            throw new SchemaSyncException("源表名不能为空");
        }

        // 禁止自同步（同一数据源 + 同名表）
        if (Objects.equals(sourceDs.getId(), targetDs.getId()) &&
                sourceTable.trim().equals((targetTable == null ? sourceTable : targetTable.trim()))) {
            throw new SchemaSyncException("禁止将表同步到自身");
        }

        // 检查目标表是否已存在（仅 syncTable 需要，generate 不需要）
        String finalTargetTable = (targetTable == null || targetTable.trim().isEmpty())
                ? sourceTable.trim()
                : targetTable.trim();
        List<String> targetTables = datasourceService.getAllTables(targetDs);
        if (targetTables.contains(finalTargetTable)) {
            throw new SchemaSyncException("目标表 " + finalTargetTable + " 已存在");
        }
    }

    // ==============================
    // 同构同步：执行 + 生成
    // ==============================

    /**
     * 处理同构同步（如 MySQL → MySQL, PG → PG）
     * <p>
     * 此方法会生成 DDL 并执行。
     */
    private SyncTableResult handleHomogeneousSync(
            Datasource sourceDs,
            Datasource targetDs,
            String sourceTable,
            String targetTable,
            DbType dbType) {

        // 1. 生成 DDL
        String finalSql = generateHomogeneousDdl(sourceDs, targetDs, sourceTable, targetTable, dbType);

        // 2. 执行
        try {
            datasourceService.executeDdl(targetDs, finalSql);
            log.info("同构同步成功：表 {} 已在目标数据源创建", targetTable);
            return SyncTableResult.success(finalSql, "表 " + targetTable + " 已通过原生SQL同步创建");
        } catch (Exception e) {
            log.error("执行同构建表SQL失败", e);
            return SyncTableResult.failure(finalSql, "执行建表语句失败: " + e.getMessage());
        }
    }

    /**
     * 生成同构同步的 DDL（不执行）
     * <p>
     * 逻辑：
     * - 从源库获取原生 SHOW CREATE TABLE
     * - 替换表名（MySQL/Doris）或 schema.table 引用（PostgreSQL）
     */
    private String generateHomogeneousDdl(
            Datasource sourceDs,
            Datasource targetDs,
            String sourceTable,
            String targetTable,
            DbType dbType) {

        String createSql = datasourceService.getCreateTableSql(sourceDs, sourceTable);
        if (createSql == null || createSql.trim().isEmpty()) {
            throw new SchemaSyncException("无法获取源表 " + sourceTable + " 的建表语句");
        }

        if (dbType == DbType.POSTGRESQL) {
            // PostgreSQL: 替换 "schema"."table" 引用
            String srcSchema = sourceDs.getEffectSchema();
            String tgtSchema = targetDs.getEffectSchema();
            String escapedSrcSchema = Pattern.quote(srcSchema);
            String escapedSrcTable = Pattern.quote(sourceTable);
            String pattern = "\"" + escapedSrcSchema + "\"\\s*\\.\\s*\"" + escapedSrcTable + "\"";
            String replacedSql = createSql.replaceAll(pattern, "\"" + tgtSchema + "\".\"" + targetTable + "\"");
            log.debug("PostgreSQL 同构 DDL 生成：替换表引用 {} -> {}", pattern, "\"" + tgtSchema + "\".\"" + targetTable + "\"");
            return replacedSql;
        } else {
            // MySQL / Doris: 替换反引号中的表名（仅替换完整标识符）
            String regex = "`" + Pattern.quote(sourceTable) + "`";
            String replacedSql = createSql.replaceAll(regex, "`" + targetTable + "`");
            log.debug("MySQL/Doris 同构 DDL 生成：替换表名 {} -> {}", sourceTable, targetTable);
            return replacedSql;
        }
    }

    // ==============================
    // 异构同步：执行 + 生成
    // ==============================

    /**
     * 处理异构同步（如 Doris → MySQL, MySQL → PG 等）
     * <p>
     * 此方法会生成 DDL 并执行。
     * <p>
     * 重要原则：
     * - 从 Doris 同步到 MySQL/PG 时，默认不生成 PRIMARY KEY
     *   （因为 Doris 的 Key 字段是逻辑去重键，不一定适合作为主键）
     * - 主键处理应由用户显式配置（本版本暂不支持，留作扩展点）
     */
    private SyncTableResult handleHeterogeneousSync(
            Datasource sourceDs,
            Datasource targetDs,
            String sourceTable,
            String targetTable,
            DbType sourceType,
            DbType targetType) {

        // 1. 生成完整 DDL（含 PG 注释）
        String fullSql = generateHeterogeneousDdl(sourceDs, targetDs, sourceTable, targetTable, sourceType, targetType);

        // 2. 执行 DDL
        try {
            if (targetType == DbType.POSTGRESQL) {
                // PostgreSQL 需要逐条执行（因包含 COMMENT ON 语句）
                String[] statements = fullSql.split("(?<=;)(?=\\s*(?:--.*?\\n)?\\s*\\S)");
                for (String stmt : statements) {
                    stmt = stmt.trim();
                    // 跳过空语句和注释行
                    if (!stmt.isEmpty() && !stmt.startsWith("--")) {
                        datasourceService.executeDdl(targetDs, stmt);
                    }
                }
            } else {
                // MySQL / Doris 等支持单条多语句或无需分割
                datasourceService.executeDdl(targetDs, fullSql);
            }
            log.info("异构建表成功：表 {} 已在目标数据源创建", targetTable);
            return SyncTableResult.success(fullSql, "表 " + targetTable + " 已成功创建于目标数据源");
        } catch (Exception e) {
            log.error("异构建表失败", e);
            return SyncTableResult.failure(fullSql, "建表失败: " + e.getMessage());
        }
    }

    /**
     * 生成异构同步的 DDL（不执行）
     * <p>
     * 步骤：
     * 1. 获取源表字段元数据
     * 2. 获取表注释
     * 3. 调用 DdlBuilderUtils 生成基础 CREATE TABLE
     * 4. 对 PostgreSQL，追加 COMMENT ON 语句
     */
    private String generateHeterogeneousDdl(
            Datasource sourceDs,
            Datasource targetDs,
            String sourceTable,
            String targetTable,
            DbType sourceType,
            DbType targetType) {

        // 1. 获取源表字段
        List<Map<String, Object>> columns = datasourceService.getTableColumns(sourceDs, sourceTable);
        if (columns == null || columns.isEmpty()) {
            throw new SchemaSyncException("源表 " + sourceTable + " 不存在或无字段");
        }

        // 2. 获取表注释
        String tableComment = datasourceService.getTableComment(sourceDs, sourceTable);

        // 3. 生成基础 CREATE TABLE 语句（DdlBuilderUtils 内部处理类型映射、默认值、注释位置等）
        String createSql = DdlBuilderUtils.buildCreateTableSql(
                columns,
                sourceType,
                targetType,
                targetTable,
                targetDs.getSchemaName(),
                tableComment,
                false  // 异构路径由本服务自己追加 PostgreSQL 注释
        );

        // 4. 构建完整脚本（含 PG 注释）
        StringBuilder fullSql = new StringBuilder(createSql);

        if (targetType == DbType.POSTGRESQL) {
            List<String> commentStatements = buildPostgreSqlCommentStatements(
                    targetDs, targetTable, tableComment, columns);
            if (!commentStatements.isEmpty()) {
                fullSql.append(String.join("\n", commentStatements));
            }
        }

        return fullSql.toString();
    }

    // ==============================
    // PostgreSQL 注释辅助方法
    // ==============================

    /**
     * 构建 PostgreSQL 的 COMMENT ON 语句列表
     * <p>
     * 安全措施：
     * - 使用 escapeSingleQuote 转义单引号（PostgreSQL 注释用单引号包裹）
     * - 严格校验 schema/table/column 名称（已由 TableNameValidator 保证）
     */
    private List<String> buildPostgreSqlCommentStatements(
            Datasource targetDs,
            String targetTable,
            String tableComment,
            List<Map<String, Object>> columns) {

        List<String> statements = new ArrayList<>();
        String schema = targetDs.getEffectSchema();

        // 表注释
        if (isNotBlank(tableComment)) {
            String safeComment = escapeSingleQuote(tableComment);
            statements.add(String.format(
                    "COMMENT ON TABLE \"%s\".\"%s\" IS '%s';",
                    schema, targetTable, safeComment
            ));
        }

        // 字段注释
        for (Map<String, Object> col : columns) {
            String colName = (String) col.get("name");
            String comment = (String) col.getOrDefault("comment", "");
            if (isNotBlank(comment)) {
                String safeComment = escapeSingleQuote(comment);
                statements.add(String.format(
                        "COMMENT ON COLUMN \"%s\".\"%s\".\"%s\" IS '%s';",
                        schema, targetTable, colName, safeComment
                ));
            }
        }
        return statements;
    }

    /**
     * 判断字符串是否非空（非 null 且去除空格后长度 > 0）
     */
    private boolean isNotBlank(String str) {
        return str != null && !str.trim().isEmpty();
    }

    /**
     * 转义 PostgreSQL 注释中的单引号：' → ''
     */
    private String escapeSingleQuote(String input) {
        return input.replace("'", "''");
    }
}
